#pragma once

#include <glibmm.h>

#include "../Shared/StringBuilder.hpp"
#include "../Shared/TmpAllocator.hpp"
#include "../Shared/GObjHelpers.hpp"

namespace BuildNG
{
  class InStream;
}

namespace CPreProc
{
  class SourceLocation
  {
  public:
    SourceLocation(int line,int pos)
      : line(line),
      pos(pos)
    {
    }

    bool operator == (const SourceLocation &b) const
    {
      return line == b.line && pos == b.pos;
    }

    int line;
    int pos;
  };

  /// A document that's ready to be used as input for a \ref Tokenizer.
  /// This means that the following operations have been performed on the
  /// text that was read from the source file:
  ///  * Trigraph replacement
  ///  * Line splicing
  ///  * replacing comments with whitespace
  class TokenizerInput : public BuildNG::Object
  {
  public:
    ~TokenizerInput();
    
    static Glib::RefPtr<TokenizerInput> create(BuildNG::InStream &src);
    static Glib::RefPtr<TokenizerInput> create(const char *src);
    static Glib::RefPtr<TokenizerInput> create(const Glib::ustring &src);

    class LineIterator
    {
      friend class TokenizerInput;

    public:
      bool            move_next();

      const char*     get_line_begin() const;
      const char*     get_line_end() const;
      Glib::ustring   to_string() const;

    private:
      Glib::RefPtr<TokenizerInput> doc;
      int i;
    };

    LineIterator      get_line_iterator() const;

    SourceLocation    get_source_location(const char *c,int tab_size = 4) const;

  private:
    TokenizerInput(BuildNG::InStream &src);

    class LogicalLine;

    std::vector<LogicalLine*> lines;

    /// Reads logical lines from an input stream. 
    /// A logical line is a single line, after the 3 operations explained
    /// above have been performed.
    class LineReader
    {
    public:
      static const int ALLOCATOR_STACK_SIZE = 512;

      LineReader(BuildNG::InStream &in);

      bool                read_logical_line(LogicalLine &logical_line);
      bool                end_of_file() const;

    private:
      BuildNG::InStream&  in;
      int                 cur_phys_line;
      bool                in_comment_block;

      void                process_trigraphs_and_append(const Glib::ustring &str,BuildNG::StringBuilder<256> &logical_line_str,LogicalLine &logical_line);
      void                strip_comments(BuildNG::StringBuilder<256> &logical_line_str,LogicalLine &line);
    };

    class LogicalLine
    { 
    public:
      LogicalLine();
      ~LogicalLine();

      const char*       get_begin() const { return begin; }
      const char*       get_end() const { return end; }

      Glib::ustring     to_string() const;

      void              clear();

      SourceLocation    get_source_location(const char *c,int tab_size) const;

      gchar*        begin;
      gchar*        end;

      struct ReplacementEvidence
      {
        int                       logical_line_pos;
        int                       num_chars_right;
        int                       num_tabs_right;

        static bool compare_pos(const ReplacementEvidence &a,const ReplacementEvidence &b)
        {
          return a.logical_line_pos < b.logical_line_pos;
        }
      };
      // this list contains evidence of changes between the physical line(s), and this
      // logical line. For example, it contains information on where comments or trigraphs
      // were skipped, or where newlines were eaten during line splicing.
      // the main purpose if this list is to find out the original line number, and line position,
      // of a certain character in the physical line, which may be used in an error report.
      BuildNG::TmpArray<ReplacementEvidence,32> replacement_evidence;

      int                         first_physical_line;

      void                        add_replacement_evidence(int logical_line_pos,int num_chars,int num_tabs);
    };
  };
}

